package com.alpha.service.initial;

import com.alpha.core.exception.AlphaException;
import com.alpha.core.http.Response;
import com.alpha.core.utils.CollectionUtil;
import com.alpha.core.utils.StringUtil;
import com.alpha.core.utils.JsonUtil;
import com.alpha.feign.common.AttributeFeignClient;
import com.alpha.feign.common.entity.Attribute;
import com.alpha.feign.common.entity.Dictionary;
import com.alpha.feign.common.DictionaryFeignClient;
import com.alpha.schema.SchemaContext;
import com.alpha.schema.SchemaSqlBuilder;
import com.alpha.schema.beans.*;
import com.alpha.service.context.AlphaApplicationContext;
import com.alpha.service.schemas.schemas_details.SchemaDetail;
import com.alpha.service.schemas.schemas_details.SchemaDetailService;
import com.alpha.service.schemas.schemas_logs.SchemaLog;
import com.alpha.service.schemas.schemas_logs.SchemaLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * Schema 初始化器，服务启动时，根据 Schema 配置创建或修改数据库表
 *
 * @author chenruwan
 * @since 2022-01-01
 */
@Slf4j
@Component
public class SchemaInitializer implements ApplicationRunner {
    private final JdbcTemplate jdbcTemplate;

    private final SchemaLogService schemaLogService;

    private final SchemaDetailService schemaDetailService;

    private final AttributeFeignClient attributeFeignClient;

    private final DictionaryFeignClient dictionaryFeignClient;

    public SchemaInitializer(JdbcTemplate jdbcTemplate, SchemaLogService schemaLogService,
                             SchemaDetailService schemaDetailService,
                             @Lazy AttributeFeignClient attributeFeignClient,
                             @Lazy DictionaryFeignClient dictionaryFeignClient) {
        this.jdbcTemplate = jdbcTemplate;
        this.schemaLogService = schemaLogService;
        this.schemaDetailService = schemaDetailService;
        this.attributeFeignClient = attributeFeignClient;
        this.dictionaryFeignClient = dictionaryFeignClient;
    }

    @PostConstruct
    @Transactional(rollbackFor = Exception.class)
    public void initSchemaTables() {
        initSchemaCoreTables();

        Schema schema = SchemaContext.getInstance().getSchema();
        if (CollectionUtil.isEmpty(schema.getTables())) {
            return; // 服务没有自定义的元数据
        }

        List<SchemaDetail> schemasDetails = schemaDetailService.list();
        for (Table table : schema.getTables()) {
            Optional<SchemaDetail> existed = schemasDetails.stream()
                    .filter(detail -> table.getName().equals(detail.getTableName())).findFirst();

            List<String> statements;
            if (existed.isPresent() && isTableExisted(table.getName())) {
                Table current = JsonUtil.fromString(existed.get().getSchemaDetail(), Table.class);
                statements = SchemaSqlBuilder.buildAlterSql(schema, table, Objects.requireNonNull(current));
            } else {
                statements = SchemaSqlBuilder.buildCreateSql(schema, table);
            }

            for (String statement : statements) {
                jdbcTemplate.update(statement);
                // 记录所有对数据库修改的语句
                schemaLogService.save(new SchemaLog(table.getName(), statement));
            }

            if (CollectionUtil.isNotEmpty(statements)) {
                // 逻辑删除数据库中所有的元数据详情，用于版本跟踪
                existed.ifPresent(schemaDetail -> schemaDetailService.removeById(schemaDetail.getId()));
                // 写入最新的元数据详情
                schemaDetailService.save(new SchemaDetail(table.getName(), JsonUtil.toString(table)));
            }
        }
    }

    @Override
    public void run(ApplicationArguments args) {
        initSchemaChoices();
        initSchemaAttributes();
    }

    private void initSchemaChoices() {
        Schema schema = SchemaContext.getInstance().getSchema();
        if (CollectionUtil.isEmpty(schema.getChoices())) {
            return;
        }

        List<Dictionary> dictionaries = new ArrayList<>();
        for (Choice choice : schema.getChoices()) {
            Dictionary dictionary = new Dictionary(choice.getName(), choice.getName(), choice.getDesc(), 0L)
                    .setChildren(new ArrayList<>());
            for (Option option : choice.getOptions()) {
                dictionary.getChildren().add(new Dictionary(option.getName(), option.getCode(), option.getValue(), null));
            }
            dictionaries.add(dictionary);
        }

        Response<Object> response = dictionaryFeignClient.saveOrUpdate(dictionaries);
        if (response.getStatus() != HttpStatus.OK.value()) {
            throw new AlphaException(StringUtil.format("字典同步失败：response = {}", response));
        }
    }

    private void initSchemaAttributes() {
        Schema schema = SchemaContext.getInstance().getSchema();
        if (CollectionUtil.isEmpty(schema.getTables())) {
            return;
        }

        List<Attribute> attributes = new ArrayList<>();
        for (Table table : schema.getTables()) {
            for (Field field : table.getFields()) {
                attributes.add(new Attribute()
                        .setServiceName(AlphaApplicationContext.getApplicationName())
                        .setTableName(table.getName())
                        .setColumnName(field.getName())
                        .setColumnType(field.getType())
                        .setVisualName(field.getDesc())
                        .setRequired(Boolean.parseBoolean(field.getNotNull()))
                        .setExtended(false)
                        .setFormat(field.getFormat()));
            }

            for (Foreign foreign : table.getForeigners()) {
                attributes.add(new Attribute()
                        .setServiceName(AlphaApplicationContext.getApplicationName())
                        .setTableName(table.getName())
                        .setColumnName(foreign.getKey())
                        .setColumnType("bigint")
                        .setVisualName(foreign.getDesc())
                        .setRequired(Boolean.parseBoolean(foreign.getNotNull()))
                        .setExtended(false));
            }
        }

        Response<Object> response = attributeFeignClient.saveOrUpdate(attributes);
        if (response.getStatus() != HttpStatus.OK.value()) {
            throw new AlphaException(StringUtil.format("字段属性同步失败：response = {}", response));
        }
    }

    /**
     * 初始化 schema 基础表
     */
    private void initSchemaCoreTables() {
        Schema schema = SchemaContext.getInstance().getCoreSchema();
        for (Table table : schema.getTables()) {
            if (!isTableExisted(table.getName())) {
                jdbcTemplate.update(SchemaSqlBuilder.buildCreateSql(schema, table).get(0));
            }
        }
    }

    private boolean isTableExisted(String table) {
        String querySql = StringUtil.format("SELECT * FROM information_schema.tables " +
                "WHERE table_schema = (SELECT database()) AND table_name = '{}'", table);
        return !jdbcTemplate.queryForList(querySql).isEmpty();
    }
}
